package com.lsh.oms.core.records;

import com.lsh.base.http.client.core.HttpClient;
import com.lsh.base.lang.DateTimes;
import com.lsh.base.lang.Lists;
import com.lsh.base.lang.Maps;
import com.lsh.oms.core.dao.other.ReturnMapper;
import com.lsh.oms.core.dao.other.ReturnRequestMapper;
import com.lsh.oms.core.dao.other.ReturnResultMapper;
import com.lsh.oms.core.enums.PayType;
import com.lsh.oms.core.exception.BusinessException;
import com.lsh.oms.core.records.core.JsonType;
import com.lsh.oms.core.records.core.Return;
import com.lsh.oms.core.records.core.ReturnRequest;
import com.lsh.oms.core.records.core.ReturnResult;
import com.lsh.oms.core.service.order.OrderQueryService;
import com.lsh.oms.core.service.po.OmsTaskService;
import com.lsh.oms.core.service.tool.Criteria;
import com.lsh.oms.core.service.tool.Pageable;
import com.lsh.oms.core.utils.BatchNumberGenerator;
import com.lsh.oms.core.utils.IdGenerator;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;

import static com.lsh.base.lang.BigDecimals.*;
import static com.lsh.base.lang.bean.Beans.*;
import static com.lsh.oms.core.enums.PayType.PAY_ON_DELIVERY;
import static com.lsh.oms.core.records.Responsibilities.DELIVERYMAN;
import static com.lsh.oms.core.records.core.Return.Status.*;
import static java.math.BigDecimal.ZERO;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

public abstract class ReturnRecord implements Return, JsonType {

    protected static final Logger LOGGER = LoggerFactory.getLogger(ReturnRecord.class);

    @Resource
    protected OrderQueryService orders;

    @Resource
    protected ReturnRequestRepository returnRequests;

    @Resource
    protected ReturnResultRepository returnResults;

    @Resource
    protected ReturnMapper mapper;

    @Resource
    protected ReturnResultMapper resultMapper;

    @Resource
    protected OmsTaskService tasks;

    @Resource(name = "apache.http.client")
    protected HttpClient https;

    @Resource
    protected BatchNumberGenerator numberGenerator;

    @Value("${mis.host}")
    protected String misHost;

    @Value("${oms.host}")
    protected String omsHost;

    @Value("${oms.base.path}")
    protected String omsBasePath;

    protected Long id;

    protected String number;

    protected Long returnRequestId;

    protected Long orderId;

    protected Long shippingId;

    protected Long receiptId;

    protected Integer zoneId;

    protected Long addressId;

    protected String warehouseId;

    protected String warehouseName;

    protected Occasion occasion;

    protected Long userId;

    protected String username;

    protected String shippingDeliveryWay;

    protected Map<String, Object> ext;

    protected Map<String, Object> draftOfCheck;

    protected String checkedBy;

    protected Status status;

    protected Boolean returnRequired;

    protected String storageType;

    protected Long deliverymanId;

    protected String routeId;

    protected BigDecimal refundAmount;

    protected Long refundNumber;

    protected String refundStatus;

    protected DateTime refundedAt;

    protected DateTime deliveredAt;

    protected DateTime returnedAt;

    protected DateTime createdAt;

    protected DateTime updatedAt;

    protected Long venderId;

    protected List<ReturnDetail> details;

    @Override
    public Map<String, Object> toJson() {
        Map<String, Object> json = new HashMap<>();
        json.put("id", id);
        Optional.ofNullable(number).ifPresent($ -> json.put("number", $));

        json.put("receipt", Maps.newHashMap("id", receiptId));
        json.put("occasion", occasion);

        Map<String, Object> order = Maps.newHashMap("id", orderId);
        Optional.ofNullable(getMap(ext, "order")).ifPresent(order::putAll);
        json.put("order", order);

        Map<String, Object> shipping = Maps.newHashMap("id", shippingId, "delivery_way", shippingDeliveryWay);
        Optional.ofNullable(getMap(ext, "shipping")).ifPresent(shipping::putAll);
        json.put("shipping", shipping);

        Map<String, Object> user = Maps.newHashMap("id", userId);
        Optional.ofNullable(getMap(ext, "user")).ifPresent(user::putAll);
        json.put("user", user);

        json.put("storage_type", storageType);

        Map<String, Object> refund = new HashMap<>();
        Optional.ofNullable(refundWay()).ifPresent($ -> refund.put("way", $));
        Optional.ofNullable(refundAmount).ifPresent($ -> refund.put("amount", $));
        Optional.ofNullable(refundedAt).ifPresent($ -> refund.put("refunded_at", $));
        Optional.ofNullable(refundStatus).ifPresent($ -> refund.put("status", $));
        Optional.ofNullable(refundNumber).ifPresent($ -> refund.put("no", $));
        json.put("refund", refund);

        json.put("amount", amount());
        json.put("actual_amount", actualAmount());
        json.put("returned_amount", returnedAmount());

        ofNullable(details).ifPresent(d ->
                json.put("aggregate", Maps.newHashMap(
                        "actual_quantity", d.stream().filter($ -> !$.isWeighingGoods()).map($ -> $.actualQuantity()).reduce(ZERO, BigDecimal::add),
                        "actual_weight", d.stream().filter($ -> $.isWeighingGoods()).map($ -> $.actualQuantity()).reduce(ZERO, BigDecimal::add),
                        "returned_quantity", d.stream().filter($ -> !$.isWeighingGoods()).map($ -> $.returnedQuantity()).reduce(ZERO, BigDecimal::add),
                        "returned_weight", d.stream().filter($ -> $.isWeighingGoods()).map($ -> $.returnedQuantity()).reduce(ZERO, BigDecimal::add)))
        );

        json.put("status", status);
        json.put("draft_of_check", draftOfCheck);

        Optional.ofNullable(checkedBy).ifPresent($ -> json.put("checked_by", checkedBy));

        Map<String, Object> returnRequest = Maps.newHashMap("id", returnRequestId);
        Optional.ofNullable(getMap(ext, "return_request")).ifPresent(returnRequest::putAll);
        json.put("return_request", returnRequest);

        json.put("warehouse", Maps.newHashMap("id", warehouseId, "name", warehouseName));

        Optional.ofNullable(deliveredAt).ifPresent($ -> json.put("delivered_at", $));
        Optional.ofNullable(returnedAt).ifPresent($ -> json.put("returned_at", $));

        Optional.ofNullable(deliverymanId).ifPresent($ -> json.put("deliveryman", Maps.newHashMap("id", $)));
        Optional.ofNullable(routeId).ifPresent($ -> json.put("route", Maps.newHashMap("id", $)));

        json.put("created_at", createdAt);
        json.put("updated_at", updatedAt);
        json.put("vender_id", venderId);
        ofNullable(details).ifPresent(details -> json.put("details", details.stream().map(item -> ((JsonType) item).toJson()).collect(toList())));
        return json;
    }

    protected String basePath() {
        return omsHost + omsBasePath;
    }

    protected BigDecimal amount() {
        return details.stream().map($ -> $.amount()).reduce(ZERO, BigDecimal::add);
    }

    protected BigDecimal actualAmount() {
        return details.stream().map($ -> $.actualAmount()).reduce(ZERO, BigDecimal::add);
    }

    protected BigDecimal returnedAmount() {
        return details.stream().map($ -> $.returnedAmount()).reduce(ZERO, BigDecimal::add);
    }

    private String refundWay() {
        Integer payType = getInt(ext, "order.pay_type");
        Optional<PayType> matched = Stream.of(PayType.values()).filter($ -> $.getValue().equals(payType)).findFirst();
        if (!matched.isPresent()) {
            return null;
        }
        PayType paymentWay = matched.get();
        if (paymentWay == PAY_ON_DELIVERY) {
            return (occasion == Occasion.BEFORE_SIGN) ? "REDUCE" : "CASH_COUPON";
        }
        return paymentWay.name();
    }

    @Override
    public Long id() {
        return id;
    }

    @Override
    public String number() {
        return number;
    }

    @Override
    public Status status() {
        return status;
    }

    @Override
    public Occasion occasion() {
        return occasion;
    }

    @Override
    public void fillDetails(List<ReturnDetail> details) {
        this.details = details;
    }

    @Override
    public void returned(Map<String, Object> request) {
        LOGGER.info("return: {} returned with request: {}", id, request);
        change($ -> $.status = RETURNED, CHECKED);
        if (refundSuccess()) {
            terminate(request);
        }
    }

    @Override
    public void terminate(Map<String, Object> request) {
        change($ -> $.status = TERMINATED, RETURNED);
    }

    @Override
    public void refundNotify(Map<String, Object> request) {
        LOGGER.info("return: {} refund notify with request: {}", id, request);
        if (refundSuccess()) {
            throw BusinessException.badRequest("重复的回调通知");
        }
        BigDecimal amount = getBigDecimal(request, "amount");
        Integer refundedAt = getInt(request, "refunded_at");
        Long number = getLong(request, "number");
        if (number == null || refundedAt == null) {
            throw BusinessException.badRequest();
        }
        this.refundAmount = (amount != null) ? amount : amount();
        this.refundedAt = DateTimes.of(refundedAt);
        this.refundStatus = "SUCCESS";
        this.refundNumber = number;
        mapper.updateRefund(this);
        resultMapper.updateStatusByReturnId(id, ReturnResult.Status.PENDING, ReturnResult.Status.TERMINATED);
        if (status == RETURNED) {
            terminate(request);
        }
    }

    protected void change(Consumer<ReturnRecord> consumer, Status... baseStatuses) {
        change(Collections.emptyMap(), consumer, baseStatuses);
    }

    protected void change(Map<String, Object> request, Consumer<ReturnRecord> consumer, Status... baseStatuses) {
        ensureStatus(baseStatuses).thenUpdate(request, $ -> consumer.accept(this));
    }

    private EnsureStatus ensureStatus(Status... baseStatuses) {
        return new EnsureStatus(baseStatuses);
    }

    class EnsureStatus {

        private final Status[] statuses;

        EnsureStatus(Status[] statuses) {
            this.statuses = statuses;
        }

        void thenUpdate(Map<String, Object> request, Consumer<ReturnRecord> consumer) {
            consumer.accept(ReturnRecord.this);
            update(request);
        }

        private void update(Map<String, Object> request) {
            int count = mapper.updateStatus(request, ReturnRecord.this, statuses);
            if (count != 1) {
                throw BusinessException.unsupportedOperation();
            }
        }
    }

    @Override
    public Pageable<ReturnResult> returnResults(Criteria criteria, long page, long pageLimit) {
        criteria.add("return-id", id.toString());
        return returnResults.query(criteria, page, pageLimit);
    }

    protected String launchTarget() {
        return basePath() + "/returns/" + id + "/run";
    }

    protected String refundTarget() {
        return basePath() + "/returns/" + id + "/refund";
    }

    protected void ensureStatusIsApplicable(Status... baseStatus) {
        if (Stream.of(baseStatus).noneMatch($ -> $ == status)) {
            throw BusinessException.unsupportedOperation();
        }
    }

    @Override
    public Optional<ReturnResult> returnResult(Long id) {
        return returnResults.find(id);
    }

    @Override
    public Optional<ReturnDetail> returnDetail(Long id) {
        return Optional.ofNullable(mapper.findDetail(id));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void check(Map<String, Object> request) {
        LOGGER.info("return: {}, check with request: {}", id, request);
        ensureStatusIsApplicable(DELIVERED);
        enhance(request);
        this.draftOfCheck = request;
        Map<String, Map<Long, Map<String, Object>>> group = groupByResponsibility();
        ensureQuantityNotOverLimit(group);
        updateReturnedQuantity(group);
        mapper.updateDraftOfCheck(this);
    }

    private void enhance(Map<String, Object> request) {
        Map<Long, ReturnDetailRecord> itemsInfo = details.stream().map($ -> ((ReturnDetailRecord) $)).collect(toMap($ -> $.id(), $ -> $));
        List<Map<String, Object>> details = getList(request, "details").stream().map($ -> {
            Map<String, Object> item = new HashMap<>($);
            ReturnDetailRecord record = itemsInfo.get(getLong(item, "id"));
            item.forEach((key, value) -> {
                if (key.endsWith("_take_responsibility")) {
                    BigDecimal quantity = getBigDecimal(value, "quantity", ZERO);
                    ((Map<String, Object>) value).put("amount", multiplyWithScale2(record.isWeighingGoods ? divideWithScale4(quantity, record.saleUnit) : quantity, record.price));
                }
            });
            return item;
        }).collect(toList());
        request.put("details", details);
    }

    private void ensureQuantityNotOverLimit(Map<String, Map<Long, Map<String, Object>>> group) {
        boolean overLimit = details.stream().anyMatch($ -> {
            BigDecimal totalQuantity = $.actualQuantity();
            BigDecimal requestQuantity = group.values().stream().map($$ -> Optional.ofNullable($$.get($.id())).map($$$ -> getBigDecimal($$$, "quantity")).orElse(ZERO)).reduce(ZERO, BigDecimal::add);
            return greaterThan(requestQuantity, totalQuantity);
        });
        if (overLimit) {
            throw BusinessException.badRequest();
        }
    }

    private void updateReturnedQuantity(Map<String, Map<Long, Map<String, Object>>> group) {
        Map<Long, BigDecimal> quantityOfNonResponsibility = quantityOfNonResponsibility(group);
        quantityOfNonResponsibility.forEach((key, value) -> mapper.updateDetailReturnedQuantity(key, value));
    }

    private Map<Long, BigDecimal> quantityOfNonResponsibility(Map<String, Map<Long, Map<String, Object>>> group) {
        Map<Long, BigDecimal> nonResponsibility = new HashMap<>();
        details.stream().map($ -> ((ReturnDetailRecord) $)).forEach(item -> {
            BigDecimal quantityLeft = item.actualQuantity;
            for (Map<Long, Map<String, Object>> responsibilities : group.values()) {
                Map<String, Object> responsibilityOfItem = responsibilities.get(item.id());
                if (responsibilityOfItem != null) {
                    quantityLeft = subtractWithScale4(quantityLeft, getBigDecimal(responsibilityOfItem, "quantity", ZERO));
                }
            }
            if (greaterThanZero(quantityLeft)) {
                nonResponsibility.put(item.id(), quantityLeft);
            }
        });
        return nonResponsibility;
    }

    private Map<String, Map<Long, Map<String, Object>>> groupByResponsibility() {
        Map<String, Map<Long, Map<String, Object>>> group = new HashMap<>();
        Stream.of(Responsibilities.values()).map($ -> $.name().toLowerCase() + "_take_responsibility").forEach(responsibility -> {
            Map<Long, Map<String, Object>> responsibilityOfItem = new HashMap<>();
            getList(this.draftOfCheck, "details").forEach(item -> {
                Optional.ofNullable(getMap(item, responsibility))
                        .filter($ -> !$.isEmpty() && greaterThanZero(getBigDecimal($, "quantity", ZERO)))
                        .ifPresent($ -> responsibilityOfItem.put(getLong(item, "id"), $));
                group.put(responsibility, responsibilityOfItem);
            });
        });
        return group;
    }

    private Map<Long, Map<String, Object>> nonResponsibility(Map<Long, BigDecimal> quantityOfNonResponsibility) {
        Map<Long, Map<String, Object>> nonResponsibilityItems = new HashMap<>();
        quantityOfNonResponsibility.forEach((k, v) -> {
            nonResponsibilityItems.put(k, Maps.newHashMap("quantity", v));
            mapper.updateDetailReturnedQuantity(k, v);
        });
        return nonResponsibilityItems;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void confirm(Map<String, Object> request) {
        LOGGER.info("return: {}, confirm with request: {}", id, request);
        ensureStatusIsApplicable(DELIVERED);
        String checkedBy = getOrDefault(this.draftOfCheck, "checked_by", "WAREHOUSE");
        Map<String, Map<Long, Map<String, Object>>> group = groupByResponsibility();
        group.put("NONE", nonResponsibility(quantityOfNonResponsibility(group)));
        group.forEach((takeResponsibilityBy, takeResponsibilityInfo) -> {
            Long id = IdGenerator.genId();
            List<Map<String, Object>> items = buildResultItems(takeResponsibilityInfo, id);
            if (!items.isEmpty()) {
                saveResult(checkedBy, takeResponsibilityBy, id, items);
            }
        });
        resultMapper.findByReturnId(id).forEach($ -> $.start(Collections.emptyMap()));
        change(Maps.newHashMap("checked_by", checkedBy), $ -> $.status = CHECKED, DELIVERED);
        if (!warehousingRequired(group)) {
            returned(Collections.emptyMap());
        }
    }

    private boolean warehousingRequired(Map<String, Map<Long, Map<String, Object>>> group) {
        return Optional.ofNullable(group.get("NONE")).filter($ -> !$.isEmpty()).isPresent() && returnRequired;
    }

    private void saveResult(String checkedBy, String key, Long id, List<Map<String, Object>> items) {
        resultMapper.insertDetails(items);
        Map<String, Object> ext = new HashMap<>(this.ext);
        ext.put("return", Maps.newHashMap(
                "id", this.id,
                "warehouse", Maps.newHashMap("id", warehouseId, "name", warehouseName),
                "zone", Maps.newHashMap("id", zoneId),
                "address", Maps.newHashMap("id", addressId),
                "occasion", occasion,
                "return_required", returnRequired,
                "checked_by", checkedBy));
        String takeResponsibility = key.replace("_take_responsibility", "").toUpperCase();
        boolean takeResponsibilityRequired = !"NONE".equals(takeResponsibility);
        boolean deliverymanTakeResponsibility = DELIVERYMAN.name().equals(takeResponsibility);
        boolean checkedByWarehouse = "WAREHOUSE".equals(checkedBy);
        resultMapper.insert(this, Maps.newHashMap(
                "id", id,
                "number", takeResponsibilityRequired ? numberGenerator.generateReturnResultNumber(orderId) : null,
                "checked_by", checkedBy,
                "status", checkedByWarehouse ? TERMINATED : (refundSuccess() ? TERMINATED : PENDING),
                "take_responsibility_required", takeResponsibilityRequired,
                "take_responsibility", takeResponsibility,
                "take_responsibility_cause_type", deliverymanTakeResponsibility ? (checkedByWarehouse ? "RETURN" : "SHIPPING") : null,
                "take_responsibility_cause_id", deliverymanTakeResponsibility ? (checkedByWarehouse ? id : shippingId) : null,
                "take_responsibility_user_id", takeResponsibilityUserId(takeResponsibility),
                "amount", items.stream().map($ -> getBigDecimal($, "amount")).reduce(ZERO, BigDecimal::add),
                "ext", ext)
        );
    }

    private Long takeResponsibilityUserId(String takeResponsibility) {
        if (DELIVERYMAN.name().equals(takeResponsibility)) {
            if (returnRequired) {
                return deliverymanId;
            } else {
                return getLong(ext, "shipping.deliveryman_id");
            }
        }
        return null;
    }

    private List<Map<String, Object>> buildResultItems(Map<Long, Map<String, Object>> value, Long id) {
        List<Map<String, Object>> items = new ArrayList<>();
        Map<Long, ReturnDetailRecord> itemsInfo = details.stream().map($ -> ((ReturnDetailRecord) $)).collect(toMap($ -> $.id(), $ -> $));
        value.forEach((k, v) -> {
            ReturnDetailRecord itemInfo = itemsInfo.get(k);
            if (itemInfo != null) {
                Map<String, Object> item = new HashMap<>();
                BigDecimal quantity = getBigDecimal(v, "quantity");

                item.put("id", IdGenerator.genId());
                item.put("return_id", this.id);
                item.put("return_result_id", id);
                item.put("item_code", itemInfo.itemCode);
                item.put("sku_id", itemInfo.skuId);
                item.put("sku_name", itemInfo.skuName);
                item.put("is_weighing_goods", itemInfo.isWeighingGoods);
                item.put("sale_unit", itemInfo.saleUnit);
                item.put("price", itemInfo.price);
                item.put("quantity", quantity);
                item.put("amount", amount(itemInfo.isWeighingGoods, quantity, itemInfo.price, itemInfo.saleUnit));

                Map<String, Object> itemExt = new HashMap<>(itemInfo.ext);
                itemExt.put("opinion", Maps.newHashMap("discarded_reason", v.get("discarded_reason")));
                item.put("ext", itemExt);
                items.add(item);
            }
        });
        return items;
    }

    private BigDecimal amount(boolean isWeighingGoods, BigDecimal quantity, BigDecimal price, BigDecimal saleUnit) {
        BigDecimal multiplier = isWeighingGoods ? divideWithScale4(quantity, saleUnit) : quantity;
        return multiplyWithScale2(price, multiplier);
    }

    private boolean refundSuccess() {
        return "SUCCESS".equals(refundStatus);
    }

    @Override
    public Map<String, List<Map<String, Object>>> relations() {
        Map<String, List<Map<String, Object>>> result = new HashMap<>();
        result.put("orders", orders.findOrders(orderId));
        result.put("shippings", orders.findShippings(shippingId));
        result.put("return_requests", findReturnRequests());
        result.put("return_results", findReturnResults());
        return result;
    }

    private List<Map<String, Object>> findReturnRequests() {
        Optional<Long> returnRequestId = ofNullable(getLong(ext, "return_request.id")).filter($ -> !getBoolean(ext, "return_request.check_immediately", false));
        Optional<ReturnRequest> returnRequest = returnRequestId.flatMap($ -> returnRequests.find($, false));
        return returnRequest.isPresent()
                ? Lists.newArrayList(Maps.of("id", returnRequest.get().id(), "number", returnRequest.get().number(), "status", returnRequest.get().status()))
                : Collections.emptyList();
    }

    private List<Map<String, Object>> findReturnResults() {
        Pageable<ReturnResult> result = returnResults.query(Criteria.of("return_id", id.toString(), "details-required", "false"), 1L, 10L);
        return result.getEntries().stream().map($ -> Maps.of("id", $.id(), "number", $.number(), "status", $.status())).collect(toList());
    }

    public static class ReturnDetailRecord implements ReturnDetail, JsonType {

        @Resource
        protected ReturnRequestMapper requestMapper;

        @Resource
        private ReturnMapper mapper;

        protected Long id;

        protected Long returnRequestId;

        protected Long returnId;

        protected Integer itemCode;

        protected Long skuId;

        protected String skuName;

        protected Long itemId;

        protected String code;

        protected Boolean isWeighingGoods;

        protected BigDecimal quantity;

        protected BigDecimal actualQuantity;

        protected BigDecimal returnedQuantity;

        protected BigDecimal saleUnit;

        protected BigDecimal price;

        protected Map<String, Object> ext;

        protected Boolean returnRequired;

        protected String notes;

        protected DateTime createdAt;

        protected DateTime updatedAt;

        @Override
        public Map<String, Object> toJson() {
            Map<String, Object> json = new HashMap<>();
            json.put("id", id);
            json.put("item_code", itemCode);
            json.put("sku", Maps.newHashMap("id", skuId, "name", skuName));
            json.put("is_weighing_goods", isWeighingGoods);
            json.put("price", price);

            json.put("quantity", quantity);
            json.put("amount", amount());
            json.put("actual_quantity", actualQuantity);
            json.put("actual_amount", actualAmount());
            json.put("returned_quantity", returnedQuantity);
            json.put("returned_amount", returnedAmount());

            json.put("sale_unit", saleUnit);
            json.put("reason", getMap(ext, "reason"));
            json.put("goods", getMap(ext, "goods"));
            json.put("notes", notes);

            Optional.ofNullable(getMap(ext, "return_request_detail")).ifPresent($ -> json.put("return_request_detail", $));
            Optional.ofNullable(getMap(ext, "shipping_detail")).ifPresent($ -> json.put("shipping_detail", $));
            Optional.ofNullable(getMap(ext, "receipt_detail")).ifPresent($ -> json.put("receipt_detail", $));

            json.put("created_at", createdAt);
            json.put("updated_at", updatedAt);
            return json;
        }

        @Override
        public Long returnId() {
            return returnId;
        }

        @Override
        public boolean isWeighingGoods() {
            return isWeighingGoods;
        }

        @Override
        public BigDecimal quantity() {
            return quantity;
        }

        @Override
        public BigDecimal actualQuantity() {
            return actualQuantity;
        }

        @Override
        public BigDecimal returnedQuantity() {
            return returnedQuantity;
        }

        @Override
        public BigDecimal amount() {
            return multiplyWithScale2(price, quantity(quantity));
        }

        @Override
        public BigDecimal actualAmount() {
            return multiplyWithScale2(price, quantity(actualQuantity));
        }

        @Override
        public BigDecimal returnedAmount() {
            return multiplyWithScale2(price, quantity(returnedQuantity));
        }

        private BigDecimal quantity(BigDecimal value) {
            return isWeighingGoods ? divideWithScale4(value, saleUnit) : value;
        }

        @Override
        public void updateActualQuantity(BigDecimal actualQuantity) {
            mapper.updateDetailActualQuantity(id, actualQuantity);
            requestMapper.updateDetailDeliveredQuantity(returnRequestId, itemCode, actualQuantity);
        }

        @Override
        public void update(Map<String, Object> request) {
            mapper.updateDetail(id, request);
        }

        @Override
        public Long id() {
            return id;
        }
    }
}
